﻿using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyModel;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using Spark.AspNetCore.Authentication;
using Spark.AspNetCore.Filters;
using Spark.Core;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Loader;
using System.Text;

namespace Spark.AspNetCore
{
    public static class ServiceCollectionExtensions
    {
        public static IServiceCollection AddSpark(this IServiceCollection services, Action<SparkBuilder> setupAction)
        {
            services.AddOptions();
            services.AddLogging();
            services.AddHttpClient();
            services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
            services.AddSingleton<IUser, HttpContextUser>();
            services.AddSingleton<IRequestScopedDataRepository, HttpDataRepository>();
            services.AddSingleton<IErrorCode, EmptyErrorCode>();

            //注册通用中间件
            services.AddSingleton<IStartupFilter, StartupFilter>();

            var builder = new SparkBuilder(services);
            setupAction?.Invoke(builder);

            return services;
        }

        /// <summary>
        /// Bucket授权认证
        /// </summary>
        public static SparkBuilder AddAuthentication(this SparkBuilder builder, IConfiguration configuration, string section = "GlobalConfig:Audience")
        {
            AuthenticationOptions config = new AuthenticationOptions();
            configuration.GetSection(section).Bind(config);
            var keyByteArray = Encoding.ASCII.GetBytes(config.Secret);
            var signingKey = new SymmetricSecurityKey(keyByteArray);
            var tokenValidationParameters = new TokenValidationParameters
            {
                ValidateIssuerSigningKey = true,
                IssuerSigningKey = signingKey,
                ValidateIssuer = true,
                ValidIssuer = config.Issuer,//发行人
                ValidateAudience = true,
                ValidAudience = config.Audience,//订阅人
                ValidateLifetime = true,
                ClockSkew = TimeSpan.Zero,
                RequireExpirationTime = true,
            };
            builder.Services.AddAuthentication(options =>
            {
                //认证middleware配置
                options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            }).AddJwtBearer(opt =>
            {
                //不使用https
                opt.RequireHttpsMetadata = false;
                opt.TokenValidationParameters = tokenValidationParameters;
            });
            builder.Services.Configure<AuthenticationOptions>(configuration.GetSection(section));
            return builder;
        }

        /// <summary>
        /// Bucket授权认证
        /// </summary>
        public static SparkBuilder AddAuthentication(this SparkBuilder builder, Action<AuthenticationOptions> configAction)
        {
            if (configAction == null) throw new ArgumentNullException(nameof(configAction));

            var config = new AuthenticationOptions();
            configAction?.Invoke(config);

            var keyByteArray = Encoding.ASCII.GetBytes(config.Secret);
            var signingKey = new SymmetricSecurityKey(keyByteArray);
            var tokenValidationParameters = new TokenValidationParameters
            {
                ValidateIssuerSigningKey = true,
                IssuerSigningKey = signingKey,
                ValidateIssuer = true,
                ValidIssuer = config.Issuer,//发行人
                ValidateAudience = true,
                ValidAudience = config.Audience,//订阅人
                ValidateLifetime = true,
                ClockSkew = TimeSpan.Zero,
                RequireExpirationTime = true,
            };
            builder.Services.AddAuthentication(options =>
            {
                //认证middleware配置
                options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            }).AddJwtBearer(opt =>
            {
                //不使用https
                opt.RequireHttpsMetadata = false;
                opt.TokenValidationParameters = tokenValidationParameters;
            });
            builder.Services.Configure(configAction);
            return builder;
        }

        public static SparkBuilder AddJwtHandler(this SparkBuilder builder, IConfiguration configuration, string section = "GlobalConfig:Audience")
        {
            builder.Services.Configure<AuthenticationOptions>(configuration.GetSection(section));
            builder.Services.AddSingleton<IJwtHandler, JwtHandler>();
            return builder;
        }

        public static SparkBuilder AddJwtHandler(this SparkBuilder builder, Action<AuthenticationOptions> configAction)
        {
            builder.Services.Configure(configAction);
            builder.Services.AddSingleton<IJwtHandler, JwtHandler>();
            return builder;
        }

        public static SparkBuilder AddCors(this SparkBuilder builder, string policyName = "CorsPolicy")
        {
            builder.Services.AddCors(options => options.AddPolicy(policyName,
                b => b.AllowAnyOrigin()
                    .AllowAnyMethod()
                    .AllowAnyHeader()
                    .AllowCredentials()));
            return builder;
        }

        public static SparkBuilder AddMvc(this SparkBuilder builder, CompatibilityVersion version = CompatibilityVersion.Version_2_1)
        {
            builder.Services.AddMvc(options => options.Filters.Add(typeof(WebApiActionFilter)))
                .AddJsonOptions(options =>
                {
                    options.SerializerSettings.ContractResolver = new DefaultContractResolver();
                    options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
                    options.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:mm:ss";
                }).SetCompatibilityVersion(version);

            //取消模型自动验证
            builder.Services.Configure<ApiBehaviorOptions>(options => options.SuppressModelStateInvalidFilter = true);
            return builder;
        }

        public static SparkBuilder AddService(this SparkBuilder builder, Type type, string[] assemblyNames, Dictionary<Type, ServiceLifetime> serviceLifetimes = null)
        {
            if (assemblyNames == null || assemblyNames.Length == 0)
                throw new ArgumentException(nameof(assemblyNames));

            foreach (var item in assemblyNames)
            {
                var defaultAssemblyNames = DependencyContext.Default.GetDefaultAssemblyNames()
                    .Where(a => a.FullName.Contains(item))
                    .ToList();

                var implementationTypes = defaultAssemblyNames
                    .SelectMany(p => AssemblyLoadContext.Default
                        .LoadFromAssemblyName(p)
                        .ExportedTypes
                        .Where(b => b.GetInterfaces().Contains(type) && b.IsAbstract).ToList())
                    .ToList();

                implementationTypes.ForEach(
                    serviceType =>
                    {
                        var impType = AppDomain.CurrentDomain
                            .GetAssemblies()
                            .SelectMany(s => s.GetTypes())
                            .FirstOrDefault(serviceType.IsAssignableFrom);

                        if (impType != null)
                        {
                            if (serviceLifetimes?.Count > 0)
                            {
                                if (serviceLifetimes.Any(p => p.Key == serviceType))
                                {
                                    var life = serviceLifetimes[serviceType];
                                    switch (life)
                                    {
                                        case ServiceLifetime.Scoped:
                                            builder.Services.AddScoped(serviceType, impType);
                                            break;

                                        case ServiceLifetime.Singleton:
                                            builder.Services.AddSingleton(serviceType, impType);
                                            break;

                                        case ServiceLifetime.Transient:
                                            builder.Services.AddTransient(serviceType, impType);
                                            break;
                                    }
                                }
                            }
                            else
                            {
                                builder.Services.AddTransient(serviceType, impType);
                            }
                        }
                    });
            }

            return builder;
        }
    }
}